/*
 * @(#) $Header$
 *
 * Copyright (C) 2006  Daniel Léonard
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package ca.forklabs.baselib.util.iterator;

import java.util.ListIterator;
import ca.forklabs.baselib.util.iterator.IterableIterator;
import ca.forklabs.baselib.util.iterator.Resources;

import java.util.NoSuchElementException;

/**
 * Class {@code InsertIterator} is a <em>bidrectional</em>
 * <em>non-alterable</em> but <em>appendable</em> iterator that adds an element
 * at the current position instead of replacing them.
 * <p>
 * It replaces the <em>alterable</em> behaviour by using the <em>appendable</em>
 * behaviour already present in the adapted iterator - the adapted iterator must
 * be <em>appendable</em> and need not be <em>alterable</em>.
 * <p>
 * Even though this iterator is <em>non-alterable</em>, its {@code #set(Object)}
 * method is supported and does not directly throw exceptions.
 * <p>
 * This iterator can be useful in copy or transform algorithms to add elements
 * to collections instead of replacing them.
 *
 * @param   <E>   the type of the elements.
 *
 * @author <a href="mailto:forklabs at gmail.com?subject=ca.forklabs.baselib.util.iterator.InsertIterator">Daniel Léonard</a>
 * @version $Revision$
 * @see #set(java.lang.Object)
 */
public class InsertIterator<E> extends IterableIterator<E> implements ListIterator<E> {

//---------------------------
// Instance variable
//---------------------------

   /** The adapted iterator. */
   private ListIterator<E> iterator;


//---------------------------
// Constructor
//---------------------------

   /**
    * Constructs the insert iterator over the given {@link ListIterator}.
    * @param   iter   the adapted iterator.
    * @exception   NullPointerException   if the parameter is {@code null}.
    */
   public InsertIterator(ListIterator<E> iter) {
      this.setIterator(iter);
      }


//---------------------------
// Accessor and mutator
//---------------------------

   /**
    * Changes the adapted iterator.
    * @param   iterator   the adapted iterator.
    * @exception   NullPointerException   if the parameter is {@code null}.
    */
   protected void setIterator(ListIterator<E> iterator) {
      if (null == iterator) {
         String message = this.getCannotAdaptIteratorErrorMessage();
         throw new NullPointerException(message);
         }
      this.iterator = iterator;
      }

   /**
    * Gets the adapted iterator.
    * @return   the adapted iterator.
    */
   protected ListIterator<E> getIterator() {
      return this.iterator;
      }


//---------------------------
// Implemented methods from java.util.ListIterator
//---------------------------

   /**
    * Inserts the specified element into the list. The element is inserted
    * immediately before the next element that would be returned by
    * {@code next()}, if any, and after the next element that would be returned
    * by {@code previous()}, if any. If the list contains no elements, the new
    * element becomes the sole element on the list. The new element is inserted
    * before the implicit cursor: a subsequent call to {@code next()} would be
    * unaffected, and a subsequent call to {@code previous()} would return the
    * new element. This call increases by one the value that would be returned
    * by a call to {@code nextIndex()} or {@code previousIndex()}.
    * @param   element   the element to insert.
    * @exception   UnsupportedOperationException   if the underlying iterator
    *                                              throws this exception.
    * @exception   ClassCastException   if the underlying iterator throws this
    *                                   exception.
    * @exception   IllegalArgumentException   if the underlying iterator throws
    *                                         this exception.
    */
   @Override
   public void add(E element) {
      ListIterator<E> iter = this.getIterator();
      iter.add(element);
      }

   /**
    * Returns {@code true} if this list iterator has more elements when
    * traversing the list in the forward direction. This iterator has more
    * elements in the forward direction if the underlying iterator has more
    * elements in the forward direction.
    * @return   {@code true} if the list iterator has more elements when
    *           traversing the list in the forward direction.
    */
   @Override
   public boolean hasNext() {
      ListIterator<E> iter = this.getIterator();
      boolean has_next = iter.hasNext();
      return has_next;
      }

   /**
    * Returns {@code true} if this list iterator has more elements when
    * traversing the list in the reverse direction. This iterator has more
    * elements in the reverse direction if the underlying iterator has more
    * elements in the reverse direction.
    * @return   {@code true} if the list iterator has more elements when
    *           traversing the list in the reverse direction.
    */
   @Override
   public boolean hasPrevious() {
      ListIterator<E> iter = this.getIterator();
      boolean has_previous = iter.hasPrevious();
      return has_previous;
      }

   /**
    * Returns the next element in the list. This method may be called repeatedly
    * to iterate through the list, or intermixed with calls to
    * {@code previous()} to go back and forth (note that alternating calls to
    * {@code next()} and {@code previous()} will return the same element
    * repeatedly).
    * @return   the next element in the list.
    * @exception   NoSuchElementException   if the iteration has no next element
    */
   @Override
   public E next() {
      ListIterator<E> iter = this.getIterator();
      E element = iter.next();
      return element;
      }

   /**
    * Returns the index of the element that would be returned by a subsequent
    * call to {@code next()} or the list size if the list iterator is at the
    * end of the list.
    * @return   the index of the element that would be returned by a subsequent
    *           call to {@code next()}, or list size if list iterator is at
    *           end of list.
    */
   @Override
   public int nextIndex() {
      ListIterator<E> iter = this.getIterator();
      int index = iter.nextIndex();
      return index;
      }

   /**
    * Returns the previous element in the list. This method may be called
    * repeatedly to iterate through the list backwards, or intermixed with calls
    * to {@code next()} to go back and forth (note that alternating calls to
    * {@code next()} and {@code previous()} will return the same element
    * repeatedly).
    * @return   the previous element in the list.
    * @exception   NoSuchElementException   if the iteration has no previous
    *                                       element
    */
   @Override
   public E previous() {
      ListIterator<E> iter = this.getIterator();
      E element = iter.previous();
      return element;
      }

   /**
    * Returns the index of the element that would be returned by a subsequent
    * call to {@code next()} or {@code -1} if the list iterator is at the
    * beginning of the list.
    * @return   the index of the element that would be returned by a subsequent
    *           call to {@code next}, or {@code -1} if list iterator
    *           is at beginning of list.
    */
   @Override
   public int previousIndex() {
      ListIterator<E> iter = this.getIterator();
      int index = iter.previousIndex();
      return index;
      }

   /**
    * Removes from the list the last element that was returned by {@code next()}
    * or {@code previous()}. This call can only be made once per call to
    * {@code next()} or {@code previous()}. It can be made only if {@code add()}
    * has not been called after the last call to {@code next()} or
    * {@code previous()}.
    * @exception   UnsupportedOperationException   if the underlying iterator
    *                                              throws this exception.
    * @exception   IllegalStateException   if the underlying iterator throws
    *                                      this exception.
    */
   @Override
   public void remove() {
      ListIterator<E> iter = this.getIterator();
      iter.remove();
      }

   /**
    * Adds the specified object instead of replacing it. To respect the
    * condition that :
    * <blockquote>
    * This call can be made only if neither {@code remove()} nor {@code add()}
    * have been called after the last call to {@code next()} or
    * {@code previous()}.
    * </blockquote>
    * the method {@code previous()} is called after adding the element.
    * This has for effect that the next call to {@code next()} will return
    * the parameter.
    * <p>
    * It also allows code like :
    * <blockquote><pre>
    * Object[] array = ...;
    * ListIterator iter = new InsertIterator(...);
    * for (int i = 0; i &lt; array.length; i++) {
    *    iter.set(array[i]);
    *    iter.next();
    *    }
    * </pre></blockquote>
    * to be used to populate a collection and be seen as replacing instead,
    * which is the goal of this class.
    *
    * @param   element   The element to add.
    * @see   #add(java.lang.Object)
    */
   @Override
   public void set(E element) {
      this.add(element);
      this.previous();
      }


//---------------------------
// Factory methods
//---------------------------

   /**
    * Creates a new inserter iterator.
    * @param   iterator   the adapted iterator.
    * @exception   NullPointerException   if the parameter is {@code null}.
    * @return   a new inserter iterator.
    */
   public static <E> InsertIterator<E> create(ListIterator<E> iterator) {
      InsertIterator<E> inserter = new InsertIterator<>(iterator);
      return inserter;
      }

//---------------------------
// Error methods
//---------------------------

   /**
    * Gets the error message telling that it is impossible to adapt the
    * iterator.
    * @return   the formatted message.
    */
   protected String getCannotAdaptIteratorErrorMessage() {
      String key = Resources.ITERATOR_CANNOT_ADAPT;
      String message = Resources.getLocalizedString(key);
      return message;
      }

   }
